#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>

#include <unistd.h>

int socket_accept(int _sockfd)
{
    int svrFd = _sockfd;
    int cliFd;

    struct sockaddr_in cliaddr;
    socklen_t clilen;
    memset(&cliaddr, 0, sizeof(cliaddr));
    memset(&clilen, 0, sizeof(clilen));

    if ((cliFd = accept(svrFd, (struct sockaddr *)&cliaddr, &clilen)) == -1)
    {
        printf("accept socket error: %s(errno: %d)", strerror(errno), errno);
    }
    else
    {
        printf("new client:[%d]\n", cliFd);
    }

    return cliFd;
}

// 业务逻辑
int exec_client_read(int sockfd)
{
    char rBuff[128] = {0};
    char wBuff[128] = {0};

    int rSize = recv(sockfd, &rBuff, sizeof(rBuff), 0);

    // 获取数据之后, 看选择性删除该套接字
    if (rSize <= 0)
    {
        // perror("read() failed");
        // exit(EXIT_FAILURE);
        printf("Connection closed from socket %d\n", sockfd);
        close(sockfd);
    }
    else if (rSize > 0)
    {
        printf("Read:%s\n", rBuff);
    }
    strcpy(wBuff, "pong");
    // 发送数据给客户端
    // write(cliFd, buff, sizeof(buff));
    int wSize = send(sockfd, wBuff, sizeof(wBuff), 0);
    // wSize返回值同 rSize

    return wSize;
}

int exec_client_close(int epFd, int __sockfd)
{
    epoll_ctl(epFd, EPOLL_CTL_DEL, __sockfd, NULL);
    close(__sockfd);
    return 0;
}

/* 将文件描述符fd上的EPOLLIN注册到epFd指示的epoll内核事件表中，
参数enable_et指定是否对fd启用ET模式 */
void addfd(int epFd, int fd, bool enable_et)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLRDHUP;
    if (enable_et)
    {
        event.events |= EPOLLET; // 注意: 这里的.events 添加了EPOLLET标识, 表示对该事件启动ET模式
    }
    epoll_ctl(epFd, EPOLL_CTL_ADD, fd, &event);
    // setnonblocking(fd);  // 设置套接字为非阻塞模式
}

int main(int argc, char **argv)
{
    int epFd = epoll_create(1024);

    struct epoll_event epEvents[1024];
    struct epoll_event epTemp;

    int svrFd;

    // 初始化Socket
    if ((svrFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
    }

    // 初始化
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(8080);

    // 将本地地址绑定到所创建的套接字上
    if (bind(svrFd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
    }

    // 开始监听是否有客户端连接
    if (listen(svrFd, 10) == -1)
    {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
    }

    addfd(epFd, svrFd, 0); // 0 水平触发; 1 边缘触发

    while (1)
    {
        int nReady = epoll_wait(epFd, epEvents, sizeof(epEvents), -1);
        printf("nReady: %d\n", nReady);
        // 有事件触发
        if (nReady > 0)
        {
            for (int i = 0; i < nReady; ++i)
            {
                printf("ne: %#x\n", epEvents[i].events);

                epTemp = epEvents[i];

                // svrFd 的事件, 一般只是新客户端连接
                if (epEvents[i].data.fd == svrFd)
                {
                    if (epTemp.events & EPOLLIN)
                    {
                        int cliFd = socket_accept(epEvents[i].data.fd);
                        if (cliFd > 0)
                        {
                            addfd(epFd, cliFd, 0); // 水平触发
                        }
                    }
                }
                else
                {
                    if (epEvents[i].events & EPOLLIN)
                    {
                        exec_client_read(epEvents[i].data.fd);
                    }
                    if (epEvents[i].events & EPOLLRDHUP)
                    {
                        exec_client_close(epFd, epEvents[i].data.fd);
                    }
                    // printf("something else happened \n");
                }
            }
        }
        else
        {
            // epoll_wait 小于 0 的情况
        }
    }
    // 客户端套接字

    close(svrFd);
}